package com.ozf.laiyw.manage.cas.auth;

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.dao.DataAccessException;
import org.apache.shiro.util.ByteSource;
import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.AccountDisabledException;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.RowMapper;

import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;
import java.security.GeneralSecurityException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class CustomQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {

    @NotNull
    private String sql;
    @Value("${shiro.encryption.method}")
    private String hashMethod;
    @Value("${shiro.encryption.hashiterations}")
    private Integer hashiterations;

    @Override
    protected HandlerResult authenticateUsernamePasswordInternal(
            UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {
        if (this.sql.isEmpty() || getJdbcTemplate() == null) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly.");
        }

        String username = credential.getUsername();
        String password = credential.getPassword();
        try {
            Map map = (Map) getJdbcTemplate().queryForObject(this.sql, new UserMapRowMapper(), username, username);
            String dbPassword = (String) map.get("password");
            String encryptionPassword = new SimpleHash(hashMethod, password, ByteSource.Util.bytes(username), hashiterations).toString();
            if (!encryptionPassword.equals(dbPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            } else {
                int status = (Integer) map.get("status");
                if (2 == status) {
                    throw new AccountLockedException("The account has been locked");
                } else if (3 == status) {
                    throw new AccountDisabledException("The account has been disabled");
                }
            }
        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0)
                throw new AccountNotFoundException(username + " not found with SQL query.");
            else
                throw new FailedLoginException("Multiple records found for " + username);
        } catch (DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
        return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
    }

    @Autowired
    public void setSql(@Value("${cas.jdbc.authn.query.sql}") final String sql) {
        this.sql = sql;
    }

    @Override
    @Autowired(required = false)
    public void setDataSource(@Qualifier("queryDatabaseDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

    class UserMapRowMapper implements RowMapper {
        @Override
        public Object mapRow(ResultSet resultSet, int i) throws SQLException {
            Map map = new HashMap();
            map.put("password", resultSet.getString("user_password"));
            map.put("status", resultSet.getInt("user_status"));
            return map;
        }
    }
}
